#include "VMRPlayer.h"
#include "Observer.h"
//#include <initguid.h>
//#include <dxerr.h>
#include "DX.h"
#include "FourPointsPrimitive.h"

#include <cassert>

bool VMRPlayer::play()
{
	if ( load() )
	{
		if ( !isVideoPlaying() )
		{
			//endThreadEvent = CreateEvent( 0, true, true, 0 );
			threadHandle = CreateThread( 0, 0, VMRPlayer::runFilterGraph, this, 0, 0/*threadID*/ );
		}
	}
	
	return true;
}

bool VMRPlayer::stop()
{
	if ( isVideoPlaying() )
	{
		if ( pMediaControl ) pMediaControl->Stop();
				
		EnterCriticalSection( &_critical_section_video_playing );
		_videoPlaying = false;
		LeaveCriticalSection( &_critical_section_video_playing );
	}
	return true;
}

bool VMRPlayer::pause()
{
	if ( isVideoPlaying() )
	{
		if ( pMediaControl ) pMediaControl->Stop();

		EnterCriticalSection( &_critical_section_video_playing );
		_videoPlaying = false;
		LeaveCriticalSection( &_critical_section_video_playing );
	}
	return true;
}

VMRPlayer::VMRPlayer( std::wstring fileURL, bool preload, bool loopVideo ) : _fileURL( fileURL ), 
																			_videoPlaying( false ), 
																			threadHandle( 0 )/*, endThreadEvent( 0 )*/, 
																			pVideoTexSurface( 0 ), 
																			pVideoTexture( 0 ),
																			pVMRfilter( 0 ), 
																			pConfig( 0 ), 
																			pNotify( 0 ), 
																			pBuild( 0 ), 
																			pGraph( 0 ), 
																			pMediaControl( 0 ), 
																			pMediaEvent( 0 ), 
																			pMediaSeeking( 0 ), 
																			Primitive( ), 
																			_loopVideo( loopVideo )
{
	InitializeCriticalSection( &_critical_section_video_playing );
	
	if ( preload )
	{
		load();
	}
}

VMRPlayer::~VMRPlayer()
{
	stop();
	unload();
	DeleteCriticalSection( &_critical_section_video_playing );
}

bool VMRPlayer::load( )
{
	if ( pVMRfilter && pConfig && pNotify && pBuild && pGraph && pMediaControl && pMediaEvent && pMediaSeeking ) return true;

	HRESULT hResult = S_OK;
	
	//pMediaSeeking->GetDuration();
	//pMediaSeeking->GetTimeFormat TIME_FORMAT_MEDIA_TIME
	// devide to 10*1000*1000 to get seconds.

	try
	{
		if ( !pVMRfilter )
		{
			if ( hResult = CoCreateInstance( CLSID_VideoMixingRenderer9, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, ( void** )&pVMRfilter ) ) 
			{
				throw std::wstring( TEXT( "VideoMixingRenderer9 don't created." ) );
			}
		}

		if ( !pConfig )
		{
			if ( hResult = pVMRfilter->QueryInterface( IID_IVMRFilterConfig9, ( void** )&pConfig ) ) 
			{
				throw std::wstring( TEXT( "Query for IID_IVMRFilterConfig9 failed." ) );
			}
		}

		if ( hResult = pConfig->SetRenderingMode( VMR9Mode_Renderless ) ) // VMRMode_Windowless
		{
			throw std::wstring( TEXT( "Setting VMR9Mode_Renderless mode failed." ) );
		}
	
		if ( !pNotify )
		{
			if ( hResult = pVMRfilter->QueryInterface( IID_IVMRSurfaceAllocatorNotify9, ( void** )&pNotify ) ) 
			{	
				throw std::wstring( TEXT( "Query for IID_IVMRSurfaceAllocatorNotify9 failed." ) );
			}
		}
		
		if ( hResult = pNotify->AdviseSurfaceAllocator( 0, this ) )
		{
			throw std::wstring( TEXT( "Setting AdviseSurfaceAllocator for Notify failed." ) );
		}
		
		if ( hResult = pNotify->SetD3DDevice( DX::_()->device, DX::_()->direct3D->GetAdapterMonitor( DX::_()->monitor ) ) ) 
		{
			throw std::wstring( TEXT( "Setting D3DDevice for Notify failed." ) );
		}
	
		if ( hResult = CoCreateInstance( CLSID_CaptureGraphBuilder2, 0, CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, ( void** )&pBuild ) ) 
		{
			throw std::wstring( TEXT( "Query IID_ICaptureGraphBuilder2 failed" ) );
		}

		if ( hResult = CoCreateInstance( CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, ( void** )&pGraph ) ) 
		{
			throw std::wstring( TEXT( "Query IID_IGraphBuilder failed" ) );
		}

		if ( hResult = pBuild->SetFiltergraph( pGraph ) ) 
		{
			throw std::wstring( TEXT( "SetFiltergraph failed" ) );
		}

#if defined(_DEBUG) || defined(DEBUG)
		hLogFile = CreateFile( TEXT("VMRLog.log"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
 
		if ( hLogFile == INVALID_HANDLE_VALUE ) {
			throw std::wstring( TEXT( "CreateFile failed" ) );
		}

		if ( hResult = pGraph->SetLogFile( ( DWORD_PTR )hLogFile ) )
		{
			throw std::wstring( TEXT( "AddFilter VMR failed" ) );
		}
#endif
		if ( hResult = pGraph->AddFilter( pVMRfilter, TEXT( "X-VMR" ) ) ) 
		{
			throw std::wstring( TEXT( "AddFilter VMR failed" ) );
		}

		if ( hResult = pGraph->QueryInterface( IID_IMediaControl, ( void** )&pMediaControl ) ) 
		{
			throw std::wstring( TEXT( "Query IID_IMediaControl failed" ) );
		}

		if ( hResult = pGraph->QueryInterface( IID_IMediaEventEx, ( void** )&pMediaEvent ) ) 
		{
			throw std::wstring( TEXT( "Query IID_IMediaEventEx failed" ) );
		}

		if ( hResult = pGraph->QueryInterface( IID_IMediaSeeking, ( void** )&pMediaSeeking ) ) 
		{
			throw std::wstring( TEXT( "Query IID_IMediaSeeking failed" ) );
		}
		
		if ( hResult = pGraph->RenderFile( fileURL().c_str() , 0 ) ) 
		{
			throw std::wstring( TEXT( "Can't read file " ) ) + fileURL();
		}

		/*std::vector< LCVertexTexture > vec;

		vec.push_back( LCVertexTexture( -1.0f, -1.0f, 0.0f, 0.0f, 0.0f ) );
		vec.push_back( LCVertexTexture(  1.0f, -1.0f, 0.0f, 1.0f, 0.0f ) );
		vec.push_back( LCVertexTexture( -1.0f,  1.0f, 0.0f, 0.0f, 1.0f ) );
		vec.push_back( LCVertexTexture(  1.0f,  1.0f, 0.0f, 1.0f, 1.0f ) );

		setBuffer( vec );*/
	}
	catch ( std::wstring string )
	{
		unload();
		throw string;
	}

	return true;
}

bool VMRPlayer::unload()
{
	if ( pVideoTexSurface ) { pVideoTexSurface->Release(); pVideoTexSurface = 0; }
	if ( pVideoTexture ) { pVideoTexture->Release(); pVideoTexture = 0; }
	if ( pVMRfilter ) 
		if ( pGraph ) pGraph->RemoveFilter( pVMRfilter );
	
	if ( pNotify ) { pNotify->Release(); pNotify = 0; }
	if ( pConfig ) { pConfig->Release(); pConfig = 0; }
	if ( pVMRfilter ) { pVMRfilter->Release(); pVMRfilter = 0; }

	if ( pMediaEvent ) 
	{
		pMediaEvent->SetNotifyWindow( 0, 0, 0 );
		pMediaEvent->Release(); pMediaEvent = 0; 
	}

	if ( pMediaSeeking ) { pMediaSeeking->Release(); pMediaSeeking = 0; }
	if ( pMediaControl ) { pMediaControl->Release(); pMediaControl = 0; }

#if defined(_DEBUG) || defined(DEBUG)
	if ( pGraph ) 
	{
		pGraph->SetLogFile( 0 );
		CloseHandle( hLogFile );
	}
#endif

	IEnumFilters* pEnum;
	IBaseFilter* pFilter = 0;
	
	if ( pGraph )
	{
		if ( pGraph->EnumFilters( &pEnum ) == S_OK ) 
		{
			HRESULT hr = S_OK;
			while( SUCCEEDED( hr = pEnum->Next( 1, &pFilter, 0 ) ) )
			{
				pGraph->RemoveFilter( pFilter );
				pFilter->Release();
			}
			
			pEnum->Release();
		}
	}

	
	if ( pGraph ) { pGraph->Release(); pGraph = 0; }
	if ( pBuild ) { pBuild->Release(); pBuild = 0; }

	return true;
}

void VMRPlayer::draw()
{
	DX::_()->device->SetTexture( 0, videoTexture() );
	
	DWORD value;

	DX::_()->device->GetTextureStageState( 0, D3DTSS_COLORARG1, &value );
	if ( value != D3DTA_TEXTURE ) DX::_()->device->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
	
	DX::_()->device->GetTextureStageState( 0, D3DTSS_COLOROP, &value );
	if ( value != D3DTOP_SELECTARG1 ) DX::_()->device->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
	
	DX::_()->device->SetFVF( LCVertexTexture::FORMAT );
	DX::_()->device->SetStreamSource( 0, FourPointsPrimitive::_()->buffer(), 0, sizeof( LCVertexTexture ) );
	DX::_()->device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );


	/*D3DXMATRIX mTransf;
	D3DXVECTOR2 scale, transl;

	float kx = 2.0f / ( float )DX::_()->d3dpp.BackBufferWidth;
	float ky = 2.0f / ( float )DX::_()->d3dpp.BackBufferHeight;

	scale.x = ( renderRect.maxX - renderRect.minX ) / ( float )DX::_()->d3dpp.BackBufferWidth;
	scale.y = ( renderRect.maxY - renderRect.minY ) / ( float )DX::_()->d3dpp.BackBufferHeight;
	transl.x = ( renderRect.maxX - renderRect.minX ) / 2.0f * kx - 1.0f + renderRect.minX * kx;
	transl.y = ( renderRect.maxY - renderRect.minY ) / 2.0f * ky - 1.0f + renderRect.minY * ky;
			
	D3DXMatrixTransformation2D( &mTransf, 0, 0, &scale, 0, 0, &transl );
	_dx->device->SetTransform( D3DTS_WORLD, &mTransf );
		
	_dx->device->SetRenderState( D3DRS_ALPHABLENDENABLE, true );
	_dx->device->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
	_dx->device->SetRenderState( D3DRS_BLENDOP, D3DBLENDOP_ADD );
	_dx->device->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
		
	_dx->device->SetRenderState( D3DRS_TEXTUREFACTOR, alpha << 24 );
			
	_dx->device->SetTexture( 0, videoTexture() );
	
	_dx->device->SetFVF( LCVertex::VERTEXFMT );
	_dx->device->SetStreamSource( 0, XApp::_()->table->pVB, 0, sizeof( LCVertex ) );
	_dx->device->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2 );
		
	_dx->device->SetRenderState( D3DRS_ALPHABLENDENABLE, false );*/
	
}

LONGLONG VMRPlayer::duration()
{
	LONGLONG _duration;
	pMediaSeeking->GetDuration( &_duration );
	return _duration;
}

LONGLONG VMRPlayer::position() const
{
	LONGLONG _position;
	pMediaSeeking->GetCurrentPosition( &_position );
	return _position;
}

void VMRPlayer::position( LONGLONG pos )
{
	pMediaSeeking->SetPositions( &pos, AM_SEEKING_AbsolutePositioning, 0, AM_SEEKING_NoPositioning );
}

DWORD __stdcall VMRPlayer::runFilterGraph( void* self )
{
	//std::assert( self );

	VMRPlayer* pl = ( VMRPlayer* )self;
	HRESULT hResult;

	if ( FAILED( hResult = pl->pMediaControl->Run( ) ) ) 
	{
		
	}
	
	EnterCriticalSection( &pl->_critical_section_video_playing );
	pl->_videoPlaying = true;
	LeaveCriticalSection( &pl->_critical_section_video_playing );

	HANDLE hTimer = CreateWaitableTimer( NULL, FALSE, NULL );
	
	if ( hTimer ) {
		int a = 60 /*d3ddm.RefreshRate*/;
		if ( !a ) { a = ( int )1000 / 60; }
		else { a = ( int )1000 / a; }
		if ( a > 100 ) { a = 100; } else if ( a < 8 ) { a = 8; } // 10..120Hz range
		LARGE_INTEGER dt;
		dt.QuadPart = -10000 * 250;
		SetWaitableTimer( hTimer, &dt, a - 1, NULL, NULL, FALSE );
	}

	while ( true )
	{
		static long evCode;
		static LONG_PTR evParam1;
		static LONG_PTR evParam2;
		static LONGLONG pos = 0;

		if ( !pl->isVideoPlaying() ) break;

		while( SUCCEEDED( pl->pMediaEvent->GetEvent( &evCode, &evParam1, &evParam2, 0 ) ) )
		{
			hResult = pl->pMediaEvent->FreeEventParams( evCode, evParam1, evParam2 );
			switch ( evCode )
			{
			case EC_USERABORT :
			case EC_ERRORABORT :
			case EC_COMPLETE :
				hResult = pl->pMediaControl->Stop( );
				pl->position( pos );
				
				if ( pl->isLoopVideo() )
				{
					//( pl->videoCompleteObservers )( 0 );
					
					pl->pMediaControl->Run();
				}
				else
				{
					pl->stop();
					
					//( pl->videoCompleteObservers )( 0 );
				}

				break;
			}
		}
		if ( hTimer ) WaitForSingleObject( hTimer, 128 );
	}

	CloseHandle( hTimer );
	//SetEvent( pl->endThreadEvent );

	return 0;
}

IDirect3DTexture9* VMRPlayer::videoTexture( )
{
	if ( !pVideoTexture )
	{
		DX::_()->device->CreateTexture( DX::_()->d3dpp.BackBufferWidth,
									DX::_()->d3dpp.BackBufferHeight,
									0, D3DUSAGE_RENDERTARGET, DX::_()->d3ddm.Format,
									D3DPOOL_DEFAULT,
									&pVideoTexture, 0 );
	}
	return pVideoTexture;
}

IDirect3DSurface9* VMRPlayer::videoTexSurface()
{
	if ( !pVideoTexSurface )
		videoTexture()->GetSurfaceLevel( 0, &pVideoTexSurface );
	return pVideoTexSurface;
}

// Alloc 
HRESULT STDMETHODCALLTYPE 
VMRPlayer::InitializeDevice( 
            /* [in] */ DWORD_PTR dwUserID,
            /* [in] */ VMR9AllocationInfo *lpAllocInfo,
			/* [out][in] */ DWORD *lpNumBuffers )
{
	HRESULT hr = S_OK;
	if ( ( lpAllocInfo->dwFlags & VMR9AllocFlag_DXVATarget ) > 0 ) 
	{
		return -1;
	}
	else if ( *lpNumBuffers < lpAllocInfo->MinBuffers )
	{
		return -1;
	}
	else if ( *lpNumBuffers < lpAllocInfo->MinBuffers )
	{
		//DEBUGLOGLN( "*lpNumBuffers < lpAllocInfo->MinBuffers failed" )
		return -1;
	}
	
	countSurfaces = *lpNumBuffers;
	pSufaces.resize( countSurfaces );
		
	hr = pNotify->AllocateSurfaceHelper( lpAllocInfo, lpNumBuffers, &pSufaces[ 0 ] );
	return hr;
	
	/*countSurfaces = *lpNumBuffers;
	
	pSufaces.resize( countSurfaces );*/
	
	//if ( lpAllocInfo->dwFlags & VMR9AllocFlag_TextureSurface )
	//{
	//	int a = 5;
	//}

	//if ( lpAllocInfo->dwFlags & VMR9AllocFlag_OffscreenSurface )
	//{
	//	for ( size_t i = 0; i < countSurfaces; ++i )
	//	{
	//		pSufaces[ i ] = 0;
	//		hr = _dx->device->CreateOffscreenPlainSurface( lpAllocInfo->dwWidth, lpAllocInfo->dwHeight, lpAllocInfo->Format, 
	//													D3DPOOL_DEFAULT, &pSufaces[ i ], NULL );
	//		if ( hr )
	//		{
	//			//DEBUGLOGLN( "CreateOffscreenPlainSurface failed" )
	//		}
	//	}
	//}

	//if ( lpAllocInfo->dwFlags & VMR9AllocFlag_3DRenderTarget )
	//{
	//	IDirect3DSurface9* back = NULL;
	//	_dx->device->GetRenderTarget( 0, &back );
	//	D3DSURFACE_DESC desc;
	//	back->GetDesc( &desc );

	//	for ( size_t i = 0; i < countSurfaces; ++i )
	//	{
	//		pSufaces[ i ] = 0;
	//		
	//		hr = _dx->device->CreateRenderTarget( lpAllocInfo->dwWidth, lpAllocInfo->dwHeight, lpAllocInfo->Format, 
	//													desc.MultiSampleType, desc.MultiSampleQuality,
	//													false, &pSufaces[ i ], NULL );

	//		if ( hr )
	//		{
	//			return hr;
	//		}
	//	}
	//	
	//}

	//return -1;
}

HRESULT STDMETHODCALLTYPE 
VMRPlayer::TerminateDevice(/* [in] */ DWORD_PTR dwID )
{
	for ( size_t i = 0; i < countSurfaces; ++i )
		pSufaces[ i ] = ( IDirect3DSurface9* )0;
	
	//cout << "TerminateDevice" << endl;
	return 0;
}
    
HRESULT STDMETHODCALLTYPE 
VMRPlayer::GetSurface( 
        /* [in] */ DWORD_PTR dwUserID,
        /* [in] */ DWORD SurfaceIndex,
        /* [in] */ DWORD SurfaceFlags,
		/* [out] */ IDirect3DSurface9 **lplpSurface )
{
	*lplpSurface = pSufaces[ SurfaceIndex ];
	//cout << "GetSurface" << endl;
	return 0;
}
    
HRESULT STDMETHODCALLTYPE 
VMRPlayer::AdviseNotify(/* [in] */ IVMRSurfaceAllocatorNotify9 *lpIVMRSurfAllocNotify )
{
	//cout << "AdviseNotify: h=" << 0 << endl;
	pNotify = lpIVMRSurfAllocNotify;
	return 0;
}

    // IVMRImagePresenter9
HRESULT STDMETHODCALLTYPE 
VMRPlayer::StartPresenting(/* [in] */ DWORD_PTR dwUserID )
{
	//cout << "StartPresenting" << endl;
	return 0;
}
    
HRESULT STDMETHODCALLTYPE 
VMRPlayer::StopPresenting(/* [in] */ DWORD_PTR dwUserID )
{
	//cout << "StopPresenting" << endl;
	return 0;
}

HRESULT STDMETHODCALLTYPE 
VMRPlayer::PresentImage(/* [in] */ DWORD_PTR dwUserID, /* [in] */ VMR9PresentationInfo *lpPresInfo )
{	
	HRESULT h;
	
	h = DX::_()->device->StretchRect( lpPresInfo->lpSurf, NULL, videoTexSurface(), 0, D3DTEXF_LINEAR );
	EnterCriticalSection( &_critical_section_video_playing );
	_videoPlaying = true;
	LeaveCriticalSection( &_critical_section_video_playing );

	return h;
}

// IUnknown
HRESULT 
VMRPlayer::QueryInterface( REFIID riid, void** ppvObject )
{
    HRESULT hr = E_NOINTERFACE;

    if( ppvObject == NULL ) {
        hr = E_POINTER;
    } 
    else if( riid == IID_IVMRSurfaceAllocator9 ) {
        *ppvObject = static_cast< IVMRSurfaceAllocator9* >( this );
        AddRef();
        hr = S_OK;
    } 
    else if( riid == IID_IVMRImagePresenter9 ) {
        *ppvObject = static_cast< IVMRImagePresenter9* >( this );
        AddRef();
        hr = S_OK;
    }
    else if( riid == IID_IUnknown ) {
        *ppvObject = static_cast< IUnknown* >( static_cast< IVMRSurfaceAllocator9* >( this ) );
        AddRef();
        hr = S_OK;
    }

    return hr;
}

ULONG 
VMRPlayer::AddRef()
{
    return InterlockedIncrement( &m_refCount );
}

ULONG 
VMRPlayer::Release()
{
    ULONG ret = InterlockedDecrement( &m_refCount );
    if( ret == 0 )
    {
        delete this;
    }

    return ret;
}

bool VMRPlayer::isVideoPlaying()
{
	return _videoPlaying;
}

std::wstring VMRPlayer::fileURL()
{
	return _fileURL;
}

bool VMRPlayer::isLoopVideo()
{
	return _loopVideo;
}